/**
 *  @author Morozan Ion
 *  
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.dddforandroid.api;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.HTTP;

import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

public class SQLiteBackup {

	public static Class<?> Kind;
	public static DefaultHttpClient authClient = new DefaultHttpClient();
	private ProgressDialog progress;
	private Context mainActivity;
	public final String ID = "id";
	public int auth = 0;
	public final static String AUTH = "authentication";
	public final static String NO_DATA = "No data available for given Database!";

	final Handler handler = new Handler() {
		public void handleMessage(Message msg) {
			auth = 1;

			if (progress.isShowing())
				progress.dismiss();
		}
	};

	public <T> void initializeDB(Class<T> Kind, Context mainActivity) {

		SQLiteBackup.Kind = Kind;

		this.mainActivity = mainActivity;

		AccountManager accountManager = AccountManager.get(mainActivity
				.getApplicationContext());
		Account[] accounts = accountManager.getAccountsByType("com.google");

		// TODO redirect when adding an email
		if (accounts.length == 0)
			return;

		Account account = accounts[0];

		progress = ProgressDialog.show(mainActivity, "", "Authenticating...",
				true);

		try {
			new ClientAuthentication(mainActivity).execute(account).get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		//TODO
		// Warning: you should call the register to c2dm only first time
		// so uncomment first time and the comment back - I have to work on this :).
//		register(mainActivity);
		showRegistrationId(mainActivity);

	}

	public Void initializeSQLite() {
		ContentResolver provider = mainActivity.getContentResolver();
		provider.delete(TypeContentProvider.CONTENT_URI, null, null);

		String serverDatabase = readServerDatabase();

		if (serverDatabase.equals(NO_DATA))
			return null;

		final JsonParser parser = new JsonParser();
		JsonElement jsonElements = null;

		jsonElements = parser.parse(serverDatabase);
		final JsonArray jsonArray = jsonElements.getAsJsonArray();

		for (JsonElement jsonEl : jsonArray) {
			JsonObject row = jsonEl.getAsJsonObject();

			if (row.get(ID).toString().replaceAll("\"", "").equals("null") == false) {
				insertInSQL(row, mainActivity);
			}
		}

		return null;
	}

	private <T> Void initializeServer(String json) {

		String id = "initialize";
		String postResponse = postData(json, authClient, id);

		Log.v("Server Message:", "*" + postResponse);
		return null;
	}

	private String postData(String result, DefaultHttpClient httpclient,
			String id) {

		String responseMessage = null;
		try {
			HttpPost post = new HttpPost(
					"http://3dforandroid.appspot.com/api/v2/" + id);

			StringEntity se = new StringEntity(result);
			se.setContentEncoding(HTTP.UTF_8);
			se.setContentType("application/json");

			post.setEntity(se);

			post.setHeader("Content-Type", "application/json");
			post.setHeader("Accept", "*/*");

			HttpResponse response = httpclient.execute(post);
			HttpEntity entity = response.getEntity();

			InputStream instream;
			instream = entity.getContent();
			responseMessage = read(instream);

		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return responseMessage;
	}

	private String read(InputStream instream) {
		StringBuilder sb = null;

		try {
			sb = new StringBuilder();

			/** Default size - 8192 characters */
			BufferedReader r = new BufferedReader(new InputStreamReader(
					instream));
			for (String line = r.readLine(); line != null; line = r.readLine()) {
				sb.append(line);
			}

			instream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return sb.toString();
	}

	private <T> String createJsonFile(Class<T> Kind) {

		String json = "{\"kind\":\"";
		json += Kind.getSimpleName();
		json += "\",\"property\":[";

		Field[] fields = Kind.getDeclaredFields();

		for (Field field : fields) {
			json += "\"" + field.getName() + "\",";
		}

		json += "\"id\"";

		json += "]}";

		return json;
	}

	private class InsertBackup extends AsyncTask<Void, Void, String> {

		private Object insertObject;
		private Context context;

		public InsertBackup(Object insertObject, Context context) {
			this.insertObject = insertObject;
			this.context = context;
		}

		@Override
		protected String doInBackground(Void... params) {

			String id = insertInSQL(insertObject, context);

			insertServer(insertObject, context, id);
			return id;
		}

	}

	public String insertObject(Object insertObject, Context context) {

		String id = null;
		try {
			id = new InsertBackup(insertObject, context).execute().get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		return id;
	}

	private String insertInSQL(Object insertObject, Context context) {

		UUID randomID = UUID.randomUUID();

		ContentValues mValues = new ContentValues();

		Field[] fields = SQLiteBackup.Kind.getDeclaredFields();
		Gson gson = new GsonBuilder().create();

		String jsonStr = gson.toJson(insertObject);

		String id = randomID.toString();
		mValues.put(ID, id);

		for (Field field : fields) {
			String value = field.getName();
			if (value != null) {
				mValues.put(field.getName(), findValue(jsonStr, value));
			}

		}
		ContentResolver resolver = context.getContentResolver();
		/** Insert in the database a new Object */
		resolver.insert(TypeContentProvider.CONTENT_URI, mValues);

		return id;

	}

	private String findValue(String jsonStr, String name) {
		if (jsonStr.contains(name)) {
			int start = jsonStr.indexOf(name) + name.length() + 3;
			String value = jsonStr.substring(start);
			int end = value.indexOf("\"");
			value = value.substring(0, end);

			return value;
		} else
			return null;
	}

	/* TODO insert only the new added Object */
	private String insertDatabaseInServer(Context mainActivity) {

		ContentResolver provider = mainActivity.getContentResolver();

		String[] projection = new String[SQLiteBackup.Kind.getDeclaredFields().length + 1];
		projection[0] = ID;
		int count = 1;

		Field[] fields = SQLiteBackup.Kind.getDeclaredFields();
		for (Field field : fields) {
			projection[count] = field.getName();
			count++;
		}
		Uri queryUri = TypeContentProvider.CONTENT_URI;

		/* Create a query */
		Cursor mCur = provider.query(queryUri, projection, null, null, null);

		/* Use the Cursor to move through the objects */
		String jsonField = "";
		if (mCur.moveToFirst()) {
			do {

				jsonField += "{";
				for (Field field : fields) {
					jsonField += "\""
							+ field.getName()
							+ "\":\""
							+ mCur.getString(mCur.getColumnIndex(field
									.getName())) + "\",";
				}

				jsonField += "\"" + ID + "\":\""
						+ mCur.getString(mCur.getColumnIndex(ID)) + "\"}";

				if (mCur.isLast() == false)
					jsonField += ",";

			} while (mCur.moveToNext());
		}

		mCur.close();

		String jsonString = createString(jsonField);

		String postId = "create/" + SQLiteBackup.Kind.getSimpleName();
		String postResponse = postData(jsonString, authClient, postId);

		Log.v("Server Message: ", postResponse);
		return postResponse;
	}

	private String insertServer(Object insertObject, Context mainActivity,
			String id) {

		Gson gson = new GsonBuilder().create();
		String jsonInsert = gson.toJson(insertObject);

		jsonInsert = jsonInsert.substring(0, jsonInsert.length() - 1);
		jsonInsert += ",\"" + ID + "\":\"" + id + "\"}";

		String jsonString = createString(jsonInsert);

		String postId = "create/" + SQLiteBackup.Kind.getSimpleName();
		String postResponse = postData(jsonString, authClient, postId);

		Log.v("Server Message:", postResponse);
		return postResponse;

	}

	private String createString(String content) {

		String output = null;

		String begin = "{\"" + SQLiteBackup.Kind.getSimpleName() + "\":[";
		String end = "]}";

		output = begin + content + end;

		return output;
	}

	private class ClientAuthentication extends
			AsyncTask<Account, Void, DefaultHttpClient> {

		private DefaultHttpClient http_client = new DefaultHttpClient();
		/* The Context of the application */
		private Context mainActivity;

		public ClientAuthentication(Context context) {

			this.mainActivity = context;
		}

		@Override
		protected void onPostExecute(DefaultHttpClient result) {
			handler.sendEmptyMessage(0);

		}

		@Override
		protected DefaultHttpClient doInBackground(Account... params) {

			AccountManager accountManager = AccountManager.get(mainActivity);
			Account account = params[0];

			try {
				Bundle bundle = accountManager.getAuthToken(account, "ah",
						false, null, null).getResult();
				Intent intent = (Intent) bundle.get(AccountManager.KEY_INTENT);

				if (intent != null) {
					// User input required
					mainActivity.startActivity(intent);
				} else {
					String auth_token = bundle
							.getString(AccountManager.KEY_AUTHTOKEN);

					// Don't follow redirects
					http_client.getParams().setBooleanParameter(
							ClientPNames.HANDLE_REDIRECTS, false);

					HttpGet http_get = new HttpGet(
							"http://3dforandroid.appspot.com/_ah"
									+ "/login?continue=http://localhost/&auth="
									+ auth_token);
					HttpResponse response = http_client.execute(http_get);

					if (response.getStatusLine().getStatusCode() != 302)
						// Response should be a redirect
						return null;

					for (Cookie cookie : http_client.getCookieStore()
							.getCookies()) {
						if (cookie.getName().equals("ACSID")) {

							authClient = http_client;

							String json = createJsonFile(Kind);
							initializeSQLite();
							initializeServer(json);
						}
					}
				}

			} catch (OperationCanceledException e) {
				e.printStackTrace();
			} catch (AuthenticatorException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}

			return http_client;
		}

	}

	public boolean modifyObject(Object modifyObject, Context context, String id) {

		boolean modified = false;
		try {
			modified = new ModifyBackup(modifyObject, context).execute(id)
					.get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return modified;
	}

	private class ModifyBackup extends AsyncTask<String, Void, Boolean> {

		private Object modifyObject;
		private Context context;

		public ModifyBackup(Object modifyObject, Context context) {
			this.modifyObject = modifyObject;
			this.context = context;
		}

		@Override
		protected Boolean doInBackground(String... id) {

			boolean modified = modifyInSQL(modifyObject, context, id[0]);

			/* If the object was modified in the SQL database, update the server */
			if (modified)
				modifyServer(modifyObject, context, id[0]);
			else
				return false;

			return true;
		}

	}

	private boolean modifyInSQL(Object modifyObject, Context context, String id) {

		ContentValues mValues = new ContentValues();

		Field[] fields = SQLiteBackup.Kind.getDeclaredFields();
		Gson gson = new GsonBuilder().create();
		modifyObject = SQLiteBackup.Kind.cast(modifyObject);

		String jsonStr = gson.toJson(modifyObject);

		for (Field field : fields) {
			String value = field.getName();
			if (value != null) {
				mValues.put(field.getName(), findValue(jsonStr, value));
			}

		}
		ContentResolver provider = mainActivity.getContentResolver();
		Uri updateUri = Uri.parse(TypeContentProvider.CONTENT_URI.toString());

		if (provider.update(updateUri, mValues, ID + " = '" + id + "'", null) != 0) {
			return true;
		}
		return false;
	}

	private String modifyServer(Object modifiedObject, Context context,
			String id) {
		Gson gson = new GsonBuilder().create();
		String JSONequiv = gson.toJson(modifiedObject);

		JSONequiv = JSONequiv.substring(0, JSONequiv.length() - 1);
		JSONequiv += ",\"" + ID + "\":\"" + id + "\"}";
		/* Use the PUT Method to update the server database */
		String putResponse = putData(id, JSONequiv, authClient);
		Log.v("Server Message:", putResponse);

		return null;
	}

	private String putData(String id, String updatedNote,
			DefaultHttpClient httpclient) {
		String responseMessage = "Error";

		try {
			HttpPut put = new HttpPut("http://3dforandroid.appspot.com/api/v2/"
					+ "update/" + SQLiteBackup.Kind.getSimpleName() + "/" + id);

			StringEntity se = new StringEntity(updatedNote);
			se.setContentEncoding(HTTP.UTF_8);
			se.setContentType("application/json");

			put.setEntity(se);

			put.setHeader("Content-Type", "application/json");
			put.setHeader("Accept", "*/*");

			HttpResponse response = httpclient.execute(put);
			HttpEntity entity = response.getEntity();

			InputStream instream;
			instream = entity.getContent();
			responseMessage = read(instream);

		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return responseMessage;
	}

	public boolean deleteObject(Object deleteObject, Context context, String id) {
		boolean deleted = false;
		try {
			deleted = new DeleteBackup(deleteObject, context).execute(id).get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}
		return deleted;
	}

	private class DeleteBackup extends AsyncTask<String, Void, Boolean> {

		private Object deleteObject;
		private Context context;

		public DeleteBackup(Object deleteObject, Context context) {
			this.deleteObject = deleteObject;
			this.context = context;
		}

		@Override
		protected Boolean doInBackground(String... id) {

			boolean deleted = deleteInSQL(deleteObject, context, id[0]);

			/* If the object was deleted in the SQL database, update the server */
			if (deleted)
				deleteServer(id[0]);
			else
				return false;

			return true;
		}

	}

	private boolean deleteInSQL(Object deleteObject, Context context, String id) {

		ContentResolver provider = mainActivity.getContentResolver();
		Uri deleteUri = Uri.parse(TypeContentProvider.CONTENT_URI.toString());

		if (provider.delete(deleteUri, ID + " = '" + id + "'", null) != 0) {

			return true;
		}
		return false;
	}

	private String deleteServer(String deleteId) {

		String deleteResponse = deleteData(deleteId, authClient);
		Log.v("Server Message: ", deleteResponse);

		return deleteResponse;
	}

	private String deleteData(String id, DefaultHttpClient httpclient) {
		String responseMessage = "Error";
		try {
			HttpDelete del = new HttpDelete(
					"http://3dforandroid.appspot.com/api/v2/delete/"
							+ SQLiteBackup.Kind.getSimpleName() + "/" + id);

			del.setHeader("Content-Type", "application/json");
			del.setHeader("Accept", "*/*");

			HttpResponse response = httpclient.execute(del);
			HttpEntity entity = response.getEntity();

			InputStream instream;
			instream = entity.getContent();
			responseMessage = read(instream);

		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return responseMessage;
	}

	public String readSQLDatabase() {

		String readObjects = new String();
		try {
			readObjects = new ReadSQLBackup().execute().get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		return readObjects;

	}

	private class ReadSQLBackup extends AsyncTask<Void, Void, String> {

		@Override
		protected String doInBackground(Void... params) {

			String json = readFromSQL();
			return json;
		}

	}

	private String readFromSQL() {

		ContentResolver provider = mainActivity.getContentResolver();

		String[] projection = new String[SQLiteBackup.Kind.getDeclaredFields().length + 1];
		projection[0] = ID;
		int count = 1;

		Field[] fields = SQLiteBackup.Kind.getDeclaredFields();

		for (Field field : fields) {
			projection[count] = field.getName();
			count++;
		}
		Uri queryUri = TypeContentProvider.CONTENT_URI;

		/* Create a query */
		Cursor mCur = provider.query(queryUri, projection, null, null, null);

		String jsonRead = "";
		if (mCur.moveToFirst()) {
			do {

				jsonRead += "{";
				for (Field field : fields) {
					jsonRead += "\""
							+ field.getName()
							+ "\":\""
							+ mCur.getString(mCur.getColumnIndex(field
									.getName())) + "\",";
				}

				jsonRead = jsonRead.substring(0, jsonRead.length() - 1);
				jsonRead += "}";

				if (mCur.isLast() == false)
					jsonRead += ",";

			} while (mCur.moveToNext());
		}

		mCur.close();
		jsonRead = createString(jsonRead);

		return jsonRead;
	}

	public String readServerDatabase() {
		String readServer = "";

		try {
			readServer = new ReadServerBackup().execute("").get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		return readServer;
	}

	public String readServerId(String id) {
		String readServer = "";

		try {
			readServer = new ReadServerBackup().execute("/" + id).get();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

		return readServer;
	}

	private class ReadServerBackup extends AsyncTask<String, Void, String> {

		@Override
		protected String doInBackground(String... id) {

			String json = getData(authClient, id[0]);
			return json;
		}

	}

	public String getData(DefaultHttpClient httpclient, String id) {
		String responseMessage = "Error";
		try {
			HttpGet get = new HttpGet(
					"http://3dforandroid.appspot.com/api/v2/get" + id
							+ "?dbName=" + SQLiteBackup.Kind.getSimpleName());

			get.setHeader("Content-Type", "application/json");
			get.setHeader("Accept", "*/*");

			HttpResponse response = httpclient.execute(get);
			HttpEntity entity = response.getEntity();

			InputStream instream = entity.getContent();
			responseMessage = read(instream);

			if (instream != null)
				instream.close();

		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return responseMessage;
	}

	// TODO private
	public void register(Context context) {
		Log.v("C2DM", "start registration process");
		Intent intent = new Intent("com.google.android.c2dm.intent.REGISTER");
		intent.putExtra("app",
				PendingIntent.getBroadcast(context, 0, new Intent(), 0));
		// Sender currently not used
		intent.putExtra("sender", "c2dmreg@gmail.com");
		context.startService(intent);
	}

	public void showRegistrationId(Context context) {
		SharedPreferences prefs = PreferenceManager
				.getDefaultSharedPreferences(context);
		String regId = prefs.getString(AUTH, "n/a");
		Toast.makeText(context, regId, Toast.LENGTH_LONG).show();
		Log.v("Preferences", regId);

	}
}
